/*---------------------------------------
- WeAct Studio Official Link
- taobao: weactstudio.taobao.com
- aliexpress: weactstudio.aliexpress.com
- github: github.com/WeActTC
- gitee: gitee.com/WeAct-TC
- blog: www.weact-tc.cn
---------------------------------------*/
#include "gd32f30x.h"
#include "string.h"
#include "stdint.h"
#include "stdio.h"
#include "math.h"
#include "DevicesCRC.h"
#include "DevicesDelay.h"
#include "DevicesSPI.h"
#include "DevicesSPIFlash.h"
#include "DriverFont.h"
#include "DriverFile.h"
#include "DriverLcd.h"


_lcd_dev lcddev;


void vLcdReset(void)
{
    LCD_RES_LOW();
    vDelayMs(5);

    LCD_RES_HIGH();
    vDelayMs(30);
}

void vLcdWriteCmd(uint8_t ucValue)
{
    LCD_CS_LOW();

    LCD_DC_LOW();
    ucLcdWriteByte(ucValue);

    LCD_CS_HIGH();
}

void vLcdWriteData(uint8_t ucValue)
{
    LCD_CS_LOW();

    LCD_DC_HIGH();
    ucLcdWriteByte(ucValue);

    LCD_CS_HIGH();
}

void vLcdWriteReg(uint8_t ucReg, uint8_t ucValue)
{
    vLcdWriteCmd(ucReg);
    vLcdWriteData(ucValue);
}

void vLcdWriteDatas(void *pvDatas, int32_t iLength)
{
    LCD_CS_LOW();

    LCD_DC_HIGH();
    cLcdWriteDatas(pvDatas, iLength);

    LCD_CS_HIGH();
}

void vLcdDmaWriteDatas(void *pvDatas, int32_t iLength)
{
    LCD_CS_LOW();

    LCD_DC_HIGH();
    cLcdDmaWriteDatas(pvDatas, iLength, 0);
}

void vLcdWritePrepare(void)
{
    vLcdWriteCmd(0x2C);
}

void vLcdDirection(uint8_t ucDirection)
{
    switch (ucDirection)
    {
        case 0:
            lcddev.width = LCD_W;
            lcddev.height = LCD_H;
            // BGR==1,MY==0,MX==0,MV==0
            vLcdWriteReg(0x36, (1 << 3) | (0 << 5) | (0 << 6) | (0 << 7));
            break;

        case 1:
            lcddev.width = LCD_H;
            lcddev.height = LCD_W;
            // BGR==1,MY==1,MX==0,MV==1
            vLcdWriteReg(0x36, (1 << 3) | (1 << 5) | (1 << 6) | (0 << 7));
            break;

        case 2:
            lcddev.width = LCD_W;
            lcddev.height = LCD_H;
            // BGR==1,MY==0,MX==0,MV==0
            vLcdWriteReg(0x36, (1 << 3) | (0 << 5) | (1 << 6) | (1 << 7));
            break;

        case 3:
            lcddev.width = LCD_H;
            lcddev.height = LCD_W;
            // BGR==1,MY==1,MX==0,MV==1
            vLcdWriteReg(0x36, (1 << 3) | (1 << 5) | (0 << 6) | (1 << 7));
            break;

        default: return;
    }

    lcddev.direction = ucDirection;
}

void vLcdSetWindows(uint32_t xStar, uint32_t yStar, uint32_t xEnd, uint32_t yEnd)
{
    xStar = (xStar >> 8) | (xStar << 8);
    xEnd  = (xEnd  >> 8) | (xEnd  << 8);
    xStar |= xEnd << 16;

    yStar = (yStar >> 8) | (yStar << 8);
    yEnd  = (yEnd  >> 8) | (yEnd  << 8);
    yStar |= yEnd << 16;

    LCD_CS_LOW();

    LCD_DC_LOW();
    ucLcdWriteByte(0x2A);
    LCD_DC_HIGH();
    cLcdWriteDatas(&xStar, 4);

    LCD_DC_LOW();
    ucLcdWriteByte(0x2B);
    LCD_DC_HIGH();
    cLcdWriteDatas(&yStar, 4);

    LCD_CS_HIGH();

    vLcdWritePrepare();
}

void vLcdClear(uint32_t xStar, uint32_t yStar, uint32_t xEnd, uint32_t yEnd, uint32_t uiColor)
{
    int32_t iLength = (xEnd - xStar) * (yEnd - yStar) * 2;
    uint16_t buf[80], i;

    uiColor = (uiColor << 8) | (uiColor >> 8);

    for(i = 0; i < (sizeof(buf) / sizeof(buf[0])); ++i)
    {
        buf[i] = uiColor;
    }

    vLcdSetWindows(xStar, yStar, xEnd - 1, yEnd - 1);

    while(iLength > 0)
    {
        vLcdWriteDatas(buf, iLength > sizeof(buf) ? sizeof(buf) : iLength);

        iLength -= sizeof(buf);
    }
}

uint32_t uiLcdGetId(void)
{
    uint32_t id = 0;

    LCD_CS_LOW();
    LCD_DC_LOW();
    ucLcdWriteByte(0x04);
    LCD_DC_HIGH();
    cLcdReadDatas(&id, 3);
    LCD_CS_HIGH();

    return id;
}

static void _st7789_init(void)
{
    //--------------------------------ST7789S Frame rate setting-------------------------
    vLcdWriteCmd(0xb2);
    vLcdWriteData(0x0c);
    vLcdWriteData(0x0c);
    vLcdWriteData(0x00);
    vLcdWriteData(0x33);
    vLcdWriteData(0x33);
    vLcdWriteCmd(0xb7);
    vLcdWriteData(0x35);

    //---------------------------------ST7789S Power setting-----------------------------
    vLcdWriteCmd(0xbb);
    vLcdWriteData(0x35);
    vLcdWriteCmd(0xc0);
    vLcdWriteData(0x0c);    // XOR RGB setting in command 36h.
    vLcdWriteCmd(0xc2);
    vLcdWriteData(0x01);
    vLcdWriteCmd(0xc3);
    vLcdWriteData(0x13);
    vLcdWriteCmd(0xc4);
    vLcdWriteData(0x20);
    vLcdWriteCmd(0xc6);
    vLcdWriteData(0x0f);
    vLcdWriteCmd(0xca);
    vLcdWriteData(0x0f);
    vLcdWriteCmd(0xc8);
    vLcdWriteData(0x08);
    vLcdWriteCmd(0x55);
    vLcdWriteData(0x90);
    vLcdWriteCmd(0xd0);
    vLcdWriteData(0xa4);
    vLcdWriteData(0xa1);

    vLcdWriteCmd(0x36);     // Memory Access Control
    vLcdWriteData(0x00);

    vLcdWriteCmd(0x3A);
    vLcdWriteData(0x05);

    //--------------------------------ST7789S gamma setting------------------------------
    vLcdWriteCmd(0xe0);
    vLcdWriteData(0xd0);
    vLcdWriteData(0x00);
    vLcdWriteData(0x06);
    vLcdWriteData(0x09);
    vLcdWriteData(0x0b);
    vLcdWriteData(0x2a);
    vLcdWriteData(0x3c);
    vLcdWriteData(0x55);
    vLcdWriteData(0x4b);
    vLcdWriteData(0x08);
    vLcdWriteData(0x16);
    vLcdWriteData(0x14);
    vLcdWriteData(0x19);
    vLcdWriteData(0x20);
    vLcdWriteCmd(0xe1);
    vLcdWriteData(0xd0);
    vLcdWriteData(0x00);
    vLcdWriteData(0x06);
    vLcdWriteData(0x09);
    vLcdWriteData(0x0b);
    vLcdWriteData(0x29);
    vLcdWriteData(0x36);
    vLcdWriteData(0x54);
    vLcdWriteData(0x4b);
    vLcdWriteData(0x0d);
    vLcdWriteData(0x16);
    vLcdWriteData(0x14);
    vLcdWriteData(0x21);
    vLcdWriteData(0x20);
    vLcdWriteCmd(0x29);
}

static void _ili9341_init(void)
{
    vLcdWriteCmd(0xCF);
    vLcdWriteData(0x00);
    vLcdWriteData(0xD9);
    vLcdWriteData(0x30);

    vLcdWriteCmd(0xED);
    vLcdWriteData(0x64);
    vLcdWriteData(0x03);
    vLcdWriteData(0x12);
    vLcdWriteData(0x81);

    vLcdWriteCmd(0xE8);
    vLcdWriteData(0x85);
    vLcdWriteData(0x10);
    vLcdWriteData(0x78);

    vLcdWriteCmd(0xCB);
    vLcdWriteData(0x39);
    vLcdWriteData(0x2C);
    vLcdWriteData(0x00);
    vLcdWriteData(0x34);
    vLcdWriteData(0x02);

    vLcdWriteCmd(0xF7);
    vLcdWriteData(0x20);

    vLcdWriteCmd(0xEA);
    vLcdWriteData(0x00);
    vLcdWriteData(0x00);

    vLcdWriteCmd(0xC0);     // Power control
    vLcdWriteData(0x21);    // VRH[5:0]

    vLcdWriteCmd(0xC1);     // Power control
    vLcdWriteData(0x12);    // SAP[2:0];BT[3:0]

    vLcdWriteCmd(0xC5);     // VCM control
    vLcdWriteData(0x32);
    vLcdWriteData(0x3C);

    vLcdWriteCmd(0xC7);     // VCM control2
    vLcdWriteData(0xC1);

    vLcdWriteCmd(0x36);     // Memory Access Control
    vLcdWriteData(0xA8);

    vLcdWriteCmd(0x3A);
    vLcdWriteData(0x55);

    vLcdWriteCmd(0xB1);
    vLcdWriteData(0x00);
    vLcdWriteData(0x18);

    vLcdWriteCmd(0xB6);     // Display Function Control
    vLcdWriteData(0x0A);
    vLcdWriteData(0xA2);

    vLcdWriteCmd(0xF2);     // 3Gamma Function Disable
    vLcdWriteData(0x00);

    vLcdWriteCmd(0x26);     // Gamma curve selected
    vLcdWriteData(0x01);

    vLcdWriteCmd(0xE0);     // Set Gamma
    vLcdWriteData(0x0F);
    vLcdWriteData(0x20);
    vLcdWriteData(0x1E);
    vLcdWriteData(0x09);
    vLcdWriteData(0x12);
    vLcdWriteData(0x0B);
    vLcdWriteData(0x50);
    vLcdWriteData(0xBA);
    vLcdWriteData(0x44);
    vLcdWriteData(0x09);
    vLcdWriteData(0x14);
    vLcdWriteData(0x05);
    vLcdWriteData(0x23);
    vLcdWriteData(0x21);
    vLcdWriteData(0x00);

    vLcdWriteCmd(0xE1);     // Set Gamma
    vLcdWriteData(0x00);
    vLcdWriteData(0x19);
    vLcdWriteData(0x19);
    vLcdWriteData(0x00);
    vLcdWriteData(0x12);
    vLcdWriteData(0x07);
    vLcdWriteData(0x2D);
    vLcdWriteData(0x28);
    vLcdWriteData(0x3F);
    vLcdWriteData(0x02);
    vLcdWriteData(0x0A);
    vLcdWriteData(0x08);
    vLcdWriteData(0x25);
    vLcdWriteData(0x2D);
    vLcdWriteData(0x0F);
    vLcdWriteCmd(0x29);
}

int8_t cLcdDesktopInit(void)
{
#define DESKTOP_PATH "./IMAGE/DESKTOP.BMP"
#define DESKTOP_PATH_RENAME "./IMAGE/DESKTOP.BMP.BACK"

    uint32_t uiAddr;
    int32_t iFileSize, iSize, iLength;
    uint16_t usCrcFile, usCrc = 0xFFFF;
    uint8_t ucBuff[512];

    /* 把SD卡的壁纸文件，复制到SPI Flash，以给后续桌面显示加速 */
    if(((iFileSize = iFileSizeGet(DESKTOP_PATH)) > 0) && (iFileSize < 1024 * 256))
    {
        usCrcFile = uiFileCrcGet(DESKTOP_PATH);

        for(uiAddr = 0, iSize = iFileSize; iSize > 0; iSize -= sizeof(ucBuff))
        {
            iLength = iSize > sizeof(ucBuff) ? sizeof(ucBuff) : iSize;

            cSPIFlashReadDatas(SPI_FLASH_DESKTOP_ADDR + uiAddr, ucBuff, iLength);
            usCrc = usCRC16_MODBUS(&usCrc, ucBuff, iLength);

            uiAddr += sizeof(ucBuff);
        }

        printf("cLcdDesktopInit crc: %08X - %08X\r\n", usCrc, usCrcFile);

        if(usCrc != usCrcFile)
        {
            for(uiAddr = 0, iSize = iFileSize; iSize > 0; iSize -= sizeof(ucBuff))
            {
                iLength = iSize > sizeof(ucBuff) ? sizeof(ucBuff) : iSize;

                cFileRead(DESKTOP_PATH, uiAddr, ucBuff, iLength);
                cSPIFlashWriteDatas(SPI_FLASH_DESKTOP_ADDR + uiAddr, ucBuff, iLength);

                uiAddr += sizeof(ucBuff);
            }
        }

        /* 重命名文件，以避免每次开机都进行读取 */
        cFileRename(DESKTOP_PATH, DESKTOP_PATH_RENAME);
    }

    return 0;
}

int8_t cLcdDesktopShow(void)
{
    /* 前54字节是BMP头部信息 */
    uint32_t uiAddress = SPI_FLASH_DESKTOP_ADDR + 54;
    int32_t iLength565, iLength888, x, y, i;
    uint16_t usRGB565;
    uint8_t bufRGB888[120], bufRGB565[80];

    for(y = lcddev.height - 1; y >= 0; --y)
    {
        for(x = 0; x < lcddev.width; x += sizeof(bufRGB565) / 2)
        {
            vLcdSetWindows(x, y, lcddev.width - 1, y);

            iLength565 = (lcddev.width - x) * 2 > sizeof(bufRGB565) ? sizeof(bufRGB565) : (lcddev.width - x) * 2;
            iLength888 = (iLength565 * 24) / 16;

            cSPIFlashReadDatas(uiAddress, bufRGB888, iLength888);
            uiAddress += iLength888;

            /* RGB888 TO RGB565 */
            for(i = 0; i < iLength888; i += 3)
            {
                usRGB565 = ((uint16_t)(bufRGB888[i + 2] & 0xF8) << 8) | ((uint16_t)(bufRGB888[i + 1] & 0xFC) << 3) | (bufRGB888[i + 0] >> 3);

                bufRGB565[(i * 2) / 3 + 1] = usRGB565;
                bufRGB565[(i * 2) / 3 + 0] = usRGB565 >> 8;
            }

            vLcdWriteDatas(bufRGB565, iLength565);
        }
    }

    return 0;
}

int8_t cLcdStringShow(int32_t iXStart, int32_t iYStart, uint32_t uiColorFront, uint32_t uiColorBack, char *cStrings)
{
    uint16_t *pusRGB565;
    uint8_t *pucFonts;
    uint32_t uiUnicode;
    uint16_t usFonts[16], usRGB565[16], n = 0;
    int8_t i, j, cSize;

    pucFonts = (uint8_t *)usFonts;
    uiColorFront = (uiColorFront << 8) | (uiColorFront >> 8);
    uiColorBack = (uiColorBack << 8) | (uiColorBack >> 8);

    while(*cStrings != 0)
    {
        cStrings += utf2uni(cStrings, &uiUnicode);

        /* 获取字模 */
        if((cSize = iFontUniLibrary16Get(uiUnicode, usFonts)) > 0)
        {
            for(i = 0; i < 16; ++i)
            {
                /* 因为LCD SPI传输方式，需要把高低Byte对调 */
                if(cSize == 32)
                    usFonts[i] = (usFonts[i] << 8) | (usFonts[i] >> 8);

                pusRGB565 = usRGB565;

                for(j = (cSize >> 1) - 1; j >= 0; --j)
                {
                    if(cSize == 32)
                        *pusRGB565++ = (usFonts[i] & (1 << j)) ? uiColorFront : uiColorBack;
                    else
                        *pusRGB565++ = (pucFonts[i] & (1 << j)) ? uiColorFront : uiColorBack;
                }

                vLcdSetWindows(iXStart + n, iYStart + i, lcddev.width - 1, iYStart + i);
                vLcdWriteDatas(usRGB565, cSize);
            }

            n += cSize >> 1;
        }
    }

    return 0;
}

void vLcdInit(void)
{
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_GPIOB);

    LCD_CS_HIGH();
    LCD_BACK_HIGH();
    LCD_DC_LOW();
    LCD_RES_HIGH();

    gpio_init(DIS_RES_GPIO_Port,    GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, DIS_RES_Pin);
    gpio_init(DIS_DC_GPIO_Port,     GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, DIS_DC_Pin);
    gpio_init(DIS_BACK_GPIO_Port,   GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, DIS_BACK_Pin);

    vSPI0Init();

    vLcdReset();

    /* Sleep out */
    vLcdWriteCmd(0x11);

    lcddev.id = uiLcdGetId();
    (lcddev.id != 0) ? _st7789_init() : _ili9341_init();

    /* 设置LCD显示方向 */
    vLcdDirection(USE_HORIZONTAL);

    vLcdClear(0, 0, lcddev.width, lcddev.height, BLACK);
}


uint16_t g_usLcdRams[3][258] = {0};
volatile uint16_t *st_pusRamNow = NULL;

void vLcdSync(void)
{
    static int32_t g_iRamRead = 0;
    uint16_t *pusRam;

    /* 标记已经传输完成 */
    if(st_pusRamNow != NULL)
        st_pusRamNow[0] = 0;

    /* 待传输的FIFO */
    pusRam = g_usLcdRams[g_iRamRead];

    st_pusRamNow = (pusRam[0] != 0) ? pusRam : NULL;

    while(cSPITransStatus(SPI0) != 0);
    LCD_CS_HIGH();

    /* FIFO已经空了 */
    if(pusRam[0] == 0)
        return;

    /* 环形FIFO */
    ++g_iRamRead;
    g_iRamRead = (g_iRamRead >= (sizeof(g_usLcdRams) / sizeof(g_usLcdRams[0])) ? 0 : g_iRamRead);

    /* 设置显示地址 */
    vLcdSetWindows(32, pusRam[1], 320 - 1, pusRam[1]);

    /* 显示数据 */
    vLcdDmaWriteDatas(&pusRam[2], 256 * 2);
}

int8_t cLcdRefresh(uint16_t usAddr, void *pvDatas, int32_t iSize)
{
    static int32_t g_iRamWrite = 0;
    volatile uint16_t *pusRam;

    pusRam = g_usLcdRams[g_iRamWrite];

    /* FIFO已经满了 */
    while(pusRam[0] == 1);

    /* 环形FIFO */
    ++g_iRamWrite;
    g_iRamWrite = (g_iRamWrite >= (sizeof(g_usLcdRams) / sizeof(g_usLcdRams[0])) ? 0 : g_iRamWrite);

    /* 标志FIFO数据有更新 */
    pusRam[0] = 1;
    pusRam[1] = usAddr;

    /* 复制新数据到FIFO */
    memcpy((void *)&pusRam[2], pvDatas, iSize);

    /* 主动触发 */
    if(st_pusRamNow == NULL)
    {
        vLcdSync();
    }

    return 0;
}
